﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace TnaguTetris
{
    class Multiplayer
    {
        // Multiplayer.cs vajaminevad objektid
        Mootor mootor;
        Kasutajaliides kasutajaliides;
        Menu menu;

        IPEndPoint ip;  // Multiplayeri osa vastase ip
        Socket socket, client;  // Socketid andmete edastuseks ja kättesaamiseks
        public bool ready = false;  // Kas ma olen mänguks valmis
        public bool opponentReady = false;
        bool host = false;  // Kas sa oled host(true) või serveriga liituja(false)
        public String unhandledText = "";  // Multiplayergia seotud veateade
        // Vastase hetkel liikuva bloki andmed on kahes järgnevas muutujas
        volatile String[] opponentCurrentBlock = new String[16] { "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" };
        volatile int[] opponentCurrentValues = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
        private const int PACKETLENGTH = 250; // Põhipakti pikkus, mis vastasele edastatakse.
        int opponentBlockColor; // Vastase klotsi värv.

        public string myName = "";  // Minu nimi multiplayer mängus
        public string opponentName = ""; // Vastase nimi multiplayer mängus
        public int leftToStart = 5;  // Counter, kuhu märgitakse stardiks jäänud sekundid
        string serverIp = "0"; // serveri ip, kuhu yhendatakse
        int serverPort = 0; // serveri port, kuhu yhendatakse

        public volatile string boardString, boardString2;  // Multiplayeris vastasele andmete saatmiseks vajalikud stringid, mis hoiavad mänguvälja infot
        volatile int[,] vastaseBoard = new int[20, 10];  // Kasutusel multiplayeris vastase väljaku joonistamiseks

        public volatile int addMyLines = 0;  // Kui see number saab 8, siis lisatakse minu väljakule 1 rida alla
        public volatile int addOpponentLines = 0; // Sama, ainult, et lisatakse vastasele
        public volatile int opponentLines = 0; // Info vastase hävitatud ridade kohta
        public volatile int opponentScore = 0; // Info vastase skoori kohta
        bool newGame = false;  // Muutuja, mis aitab uut mängu tehes minu ja vastase positsiooni sünkroniseerida
        public volatile bool amIwinner = false; // Kas mina võitsin mängu ?
        public volatile bool isOpponentWinner = false;  // kas vastane võitis mängu ?

        Random myRandom;  // Random objekt random numbrite genereerimiseks

        /// <summary>
        /// Konstruktor, mis loob multiplayer objekti.
        /// </summary>
        /// <param name="host">Kui saab sisse väärtuse host, siis tähendab, et see inimene on mängu looja</param>
        /// <param name="state"></param>
        /// <param name="mootor"></param>
        /// <param name="menu"></param>
        public Multiplayer(String host, int state, Mootor mootor, Menu menu)
        {
            this.mootor = mootor;
            kasutajaliides = new Kasutajaliides();
            this.menu = menu;
            if(host.Equals("host"))
                this.host = true;
            if (state != Menu.MultiplayerReadyIntValue)
            {
                menu.SetState(state, 0);
            }
            else
            {
                this.menu.SetState(state, 2);
            }

            opponentBlockColor = 0;
            boardString = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
            boardString2 = "";
            myRandom = new Random();
        }


        /// <summary>
        /// Ühendab mängija serveriga.
        /// </summary>
        public void JoinToServer(String serverIp, int serverPort)
        {
            this.serverIp = serverIp;
            this.serverPort = serverPort;
            Thread join = new Thread(new ThreadStart(JoinTo));
            join.IsBackground = true;
            join.Start();
        }

        /// <summary>
        /// Serveriga ühenduv thread, mis jääb peale edukat ühendamist andmepakette ootama
        /// </summary>
        void JoinTo()
        {
            ip = new IPEndPoint(IPAddress.Parse(serverIp), serverPort);
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                socket.Connect(ip);
                menu.SetState(Menu.MultiplayerReadyIntValue, 2);
                while (true)
                {
                    while (menu.GetState() == Menu.MultiplayerReadyIntValue)
                    {
                        string stringData = "";
                        int receivedDataLength = 0;
                            byte[] data = new byte[1024];
                            receivedDataLength = socket.Receive(data);
                            stringData = Encoding.ASCII.GetString(data, 0, receivedDataLength);

                            if (stringData.StartsWith("name:"))
                            {
                                OpponentReady(stringData);
                                newGame = true;
                            }                        

                        byte[] data2 = new byte[1024];
                        data2 = Encoding.ASCII.GetBytes("True");
                        socket.Send(data2, data2.Length, SocketFlags.None);
                        

                        if (stringData.Equals("True") || newGame || (ready && opponentReady))
                        {

                            if (!opponentReady)
                            {
                                try
                                {
                                    byte[] data3 = new byte[1024];
                                    receivedDataLength = socket.Receive(data3);
                                    stringData = Encoding.ASCII.GetString(data3, 0, receivedDataLength);
                                    if (stringData.StartsWith("name:"))
                                        OpponentReady(stringData);
                                }
                                catch (Exception)
                                {
                                    unhandledText = "Connection lost";
                                    socket.Dispose();
                                    menu.SetState(Menu.MultiplayerIntValue, 0);
                                }
                            }
                            while (!ready)
                            {
                            }
                            for (int i = 0; i < 10; i++)
                            {
                                SendPacket(null);
                            }
                            leftToStart = 5;
                            Thread.Sleep(1000);
                            leftToStart = 4;
                            Thread.Sleep(1000);
                            leftToStart = 3;
                            Thread.Sleep(1000);
                            leftToStart = 2;
                            Thread.Sleep(1000);
                            leftToStart = 1;
                            mootor.newGame();
                            mootor.SetLevel(0);
                            mootor.dropSpeed = 100;
                            mootor.SetScoreLimit(300);
                            opponentLines = 0;
                            opponentScore = 0;
                            addMyLines = 0;
                            addOpponentLines = 0; 
                            amIwinner = false;
                            isOpponentWinner = false;
                            Thread.Sleep(1000);
                            menu.SetState(Menu.MultiplayerPlayingIntValue, 0);
                            ready = false;
                            opponentReady = false;
                            newGame = false;



                            while (menu.GetState() == Menu.MultiplayerPlayingIntValue || menu.GetState() == Menu.MultiplayerGameOverIntValue)
                            {
                                data2 = new byte[1024];
                                receivedDataLength = socket.Receive(data2);
                                stringData = Encoding.ASCII.GetString(data2, 0, receivedDataLength);
                                if (stringData.Length == PACKETLENGTH)
                                    OpponentBoard(stringData);
                                if (stringData.StartsWith("name:"))
                                    OpponentReady(stringData);
                            }
                        }
                    }
                    if (menu.GetState() == Menu.MultiplayerIntValue)
                        break;
                }
            }
            catch (SocketException)
            {
                unhandledText = "Connection lost or wrong ip and port";
                socket.Dispose();
                menu.SetState(Menu.MultiplayerIntValue, 0);                
            }
            //OpponentAlive = false;
            //socket.Shutdown(SocketShutdown.Both);
            socket.Dispose();
        }

        /// <summary>
        /// Loob serveri, ootamaks teist mängijat.
        /// </summary>
        public void CreateServer()
        {
            Thread listen = new Thread(new ThreadStart(HandleServer));
            listen.IsBackground = true;
            listen.Start();
        }

        /// <summary>
        /// Serveri thread, mis ootab vastast ja kui ühendus olemas hakkab kuulama socketisse saabuvaid pakette
        /// </summary>
        void HandleServer()
        {
            try
            {
                ip = new IPEndPoint(IPAddress.Any, kasutajaliides.GetOption("port"));
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                socket.Bind(ip);
                socket.Listen(10);

                client = socket.Accept();
                menu.SetState(Menu.MultiplayerReadyIntValue, 2);
                IPEndPoint clientep = (IPEndPoint)client.RemoteEndPoint;

                while (true)
                {
                    while (menu.GetState() == Menu.MultiplayerReadyIntValue)
                    {
                        byte[] data = new byte[1024];
                        data = Encoding.ASCII.GetBytes("True");
                        client.Send(data, data.Length, SocketFlags.None);

                        byte[] data2;
                        int receivedDataLength = 0;
                        string stringData = "";
                        data2 = new byte[1024];
                        receivedDataLength = client.Receive(data2);
                        stringData = Encoding.ASCII.GetString(data2, 0, receivedDataLength);
                        if (stringData.StartsWith("name:"))
                        {
                            OpponentReady(stringData);
                            newGame = true;
                        }

                        if (stringData.Equals("True") || newGame || (ready && opponentReady))
                        {
                            
                                if (!opponentReady)
                                {
                                    try
                                    {
                                        byte[] data3 = new byte[1024];
                                        receivedDataLength = client.Receive(data3);
                                        stringData = Encoding.ASCII.GetString(data3, 0, receivedDataLength);
                                        if (stringData.StartsWith("name:"))
                                            OpponentReady(stringData);
                                    }
                                    catch (Exception)
                                    {
                                        unhandledText = "Connection lost";
                                        socket.Dispose();
                                        client.Dispose();
                                        menu.SetState(Menu.MultiplayerIntValue, 0);
                                    }
                                }
                            while (!ready)
                            {
                            }
                            for (int i = 0; i < 10; i++)
                            {
                                SendPacket(null);
                            }
                            leftToStart = 5;
                            Thread.Sleep(1000);
                            leftToStart = 4;
                            Thread.Sleep(1000);
                            leftToStart = 3;
                            Thread.Sleep(1000);
                            leftToStart = 2;
                            Thread.Sleep(1000);
                            leftToStart = 1;
                            mootor.newGame();
                            mootor.SetLevel(0);
                            mootor.dropSpeed = 100;
                            mootor.SetScoreLimit(300);
                            opponentLines = 0;
                            opponentScore = 0;
                            amIwinner = false;
                            isOpponentWinner = false;
                            addMyLines = 0;
                            addOpponentLines = 0; 
                            Thread.Sleep(1000);                            
                            menu.SetState(Menu.MultiplayerPlayingIntValue, 0);
                            ready = false;
                            opponentReady = false;
                            newGame = false;
                            while (menu.GetState() == Menu.MultiplayerPlayingIntValue || menu.GetState() == Menu.MultiplayerGameOverIntValue)
                            {
                                try
                                {
                                    data2 = new byte[1024];
                                    receivedDataLength = client.Receive(data2);
                                    stringData = Encoding.ASCII.GetString(data2, 0, receivedDataLength);
                                    if (stringData.Length == PACKETLENGTH)
                                        OpponentBoard(stringData);
                                    if (stringData.StartsWith("name:"))
                                        OpponentReady(stringData);
                                }
                                catch (Exception)
                                {
                                    unhandledText = "Connection lost";
                                    socket.Dispose();
                                    client.Dispose();
                                    menu.SetState(Menu.MultiplayerIntValue, 0);
                                }
                            }
                        }
                    }
                    if (menu.GetState() == Menu.MultiplayerIntValue)
                        break;
                }
                //OpponentAlive = false;
                client.Dispose();
                //socket.Shutdown(SocketShutdown.Both);
                socket.Dispose();
            }
            catch (Exception)
            {
                unhandledText = "Connection lost";
                CloseSockets();
                menu.SetNewState(Menu.MultiplayerIntValue, 0);
            }
        }

        /// <summary>
        /// Paneb stringi kujul saadud andmed  massiividesse, et draw meetodis oleks neid kergem lugeda.
        /// </summary>
        /// <param name="stringData">Vastase saadetud info klotside asukoha kohta</param>
        public void OpponentBoard(string stringData)
        {
            char[] array = new char[stringData.Length];
            array = stringData.ToCharArray();
            for (int i = 0; i < 20; i++)
            {
                for (int j = 0; j < 10; j++)
                {
                    vastaseBoard[i, j] = Int32.Parse(array[i * 10 + j].ToString());
                }
            }
            for (int j = 200; j < 216; j++)
            {
                opponentCurrentBlock[j - 200] = array[j].ToString();
            }
            opponentBlockColor = Int32.Parse(array[216].ToString());
            addMyLines += Int32.Parse(array[217].ToString());

            string scoreString = "";
            bool startPlace = false;
            for (int i = 218; i < 228; i++)
            { 
                if(!array[i].ToString().Equals('0')){
                    startPlace = true;
                }
                if(startPlace == true){
                scoreString += array[i].ToString();
                }
            }
            opponentScore = Int32.Parse(scoreString);

            string linesString = "";
            startPlace = false;
            for (int i = 228; i < 238; i++)
            {
                if (!array[i].ToString().Equals('0'))
                {
                    startPlace = true;
                }
                if (startPlace == true)
                {
                    linesString += array[i].ToString();
                }
            }
            opponentLines = Int32.Parse(linesString);

            if (array[238].ToString().Equals("1"))
            {
                isOpponentWinner = true;
            }
            else
            {
                isOpponentWinner = false;
            }

            if (array[239].ToString().Equals("1"))
            {
                amIwinner = true;
            }
            else
            {
                amIwinner = false;
            }

            string timeString = "";
            startPlace = false;
            for (int i = 240; i < 250; i++)
            {
                if (!array[i].ToString().Equals('0'))
                {
                    startPlace = true;
                }
                if (startPlace == true)
                {
                    timeString += array[i].ToString();
                }
            }
            double time = Double.Parse(timeString);
            if(((mootor.time +500) < time) && menu.GetState() == Menu.MultiplayerPlayingIntValue && !amIwinner)
            {
                isOpponentWinner = true;
                SendPacket(null);

            }

        }

        /// <summary>
        /// Saadab vastasele andmepakette infoga klotside asukohast
        /// </summary>
        public void SendPacket(string name)
        {
            try
            {
                if (name == null)
                {
                    if (!host)
                    {
                        byte[] data = new byte[1024];
                        data = Encoding.ASCII.GetBytes(boardString+(addOpponentLines/8)+AddScore()+AddLines()+AddWinner()+AddControlTime());
                        if (addOpponentLines >= 8)
                        {
                            addOpponentLines -= ((int)addOpponentLines/8)*8;
                        }
                        socket.Send(data, data.Length, SocketFlags.None);
                    }
                    else
                    {
                        byte[] data = new byte[1024];
                        data = Encoding.ASCII.GetBytes(boardString+(addOpponentLines/8)+AddScore()+AddLines()+AddWinner()+AddControlTime());
                        if (addOpponentLines >= 8)
                        {
                            addOpponentLines -= ((int)addOpponentLines / 8) * 8;
                        }
                        client.Send(data, data.Length, SocketFlags.None);
                    }
                }
                else
                {                    
                    ready = true;
                    if (!host)
                    {
                        byte[] data = new byte[1024];
                        data = Encoding.ASCII.GetBytes("name:"+name);
                        socket.Send(data, data.Length, SocketFlags.None);
                    }
                    else
                    {
                        byte[] data = new byte[1024];
                        data = Encoding.ASCII.GetBytes("name:"+name);
                        client.Send(data, data.Length, SocketFlags.None);
                    }
                }
            }
            catch (Exception)
            {
                menu.SetState(Menu.MultiplayerIntValue, 0);
                unhandledText = "Connection lost";
            }
        }

        /// <summary>
        /// Meetod aja saamiseks stringi kujul, kus on alati 10 tähemärki, 0 lisatakse vajadusel ette
        /// </summary>
        /// <returns>Minu aeg stringi kujul</returns>
        private string AddControlTime()
        {
            double time = mootor.time;
            string temp = time.ToString();
            string result = "";
            for (int i = temp.Length; i < 10; i++)
            {
                result += "0";
            }
            result += temp;
            return result;
        }

        /// <summary>
        /// Kodeerib ära võitja ja kaotaja
        /// </summary>
        /// <returns>string kujul võitja ja kaotaja</returns>
        private string AddWinner()
        {
            string temp = "";
            if (amIwinner)
                temp += "1";
            else
                temp += "0";

            if (isOpponentWinner)
                temp += "1";
            else
                temp += "0";

            return temp;
        }

        /// <summary>
        /// Meetod minu hävitatud ridade arvu kodeerimiseks
        /// </summary>
        /// <returns>String kujul minu hävitatud ridade arv</returns>
        private string AddLines()
        {
            int lines = mootor.lines;
            string temp = lines.ToString();
            string result = "";
            for (int i = temp.Length; i < 10; i++)
            {
                result += "0";
            }
            result += temp;
            return result;
        }

        /// <summary>
        /// Minu skoori kodeerimise meetod
        /// </summary>
        /// <returns>String kujul minu skoor</returns>
        private string AddScore()
        {
            int score = mootor.score;
            string temp = score.ToString();
            string result = "";
            for (int i = temp.Length; i < 10; i++)
            {
                result += "0";
            }
            result += temp;
                return result;
        }


        /// <summary>
        /// Tegeleb multiplayer loogika joonistamisega.
        /// </summary>
        /// <param name="spriteBatch">Põhiklassi joonistaja</param>
        /// <param name="boardGrid">mänguväljak(ruuduline osa)</param>
        /// <param name="blockGray">hall blokk</param>
        /// <param name="boardGridShadow">taustavari</param>
        /// <param name="blockColors">klotside värvid</param>
        /// <param name="grayY">grayY väärtus</param>
        public void Draw(SpriteBatch spriteBatch, Texture2D boardGrid, Texture2D blockGray, 
            Texture2D boardGridShadow, Texture2D[] blockColors, int grayY)
        {
            spriteBatch.Draw(boardGrid, new Vector2(466f, 22f), Color.White);
            for (int i = 0; i < vastaseBoard.GetLength(0); i++)
            {
                for (int j = 0; j < vastaseBoard.GetLength(1); j++)
                {
                    if (vastaseBoard[i, j] == 1)
                    {
                        spriteBatch.Draw(blockGray, new Vector2(j * 24 + 466, i * 24 + 22 + grayY), Color.White);
                    }
                }
            }
            computeOppunentValues();
            spriteBatch.Draw(blockColors[opponentBlockColor], new Vector2(opponentCurrentValues[0] * 24 + 466, opponentCurrentValues[4] * 24 + 22), Color.White);
            spriteBatch.Draw(blockColors[opponentBlockColor], new Vector2(opponentCurrentValues[1] * 24 + 466, opponentCurrentValues[5] * 24 + 22), Color.White);
            spriteBatch.Draw(blockColors[opponentBlockColor], new Vector2(opponentCurrentValues[2] * 24 + 466, opponentCurrentValues[6] * 24 + 22), Color.White);
            spriteBatch.Draw(blockColors[opponentBlockColor], new Vector2(opponentCurrentValues[3] * 24 + 466, opponentCurrentValues[7] * 24 + 22), Color.White);

            // vari vastase platsi ymber
            spriteBatch.Draw(boardGridShadow, new Vector2(465f, 21f), Color.White);
        }

        /// <summary>
        /// Leiab vastase liikuva bloki koordinaadid ja paneb need massiivi kujul [x1,x2,x3,x4,y1,y2,y3,y4]
        /// </summary>
        public void computeOppunentValues()
        {
            for (int i = 0; i < 16; i += 2)
            {
                opponentCurrentValues[i / 2] = Int32.Parse(opponentCurrentBlock[i] + opponentCurrentBlock[i + 1]);
            }
        }


        /// <summary>
        /// Loeb stringimassiivi hetkel liikuva klotis koordinaadid, et neid oleks mugavam vastasele edastada
        /// </summary>
        public String readCurrentBlockCoordinates(int[,] current_block)
        {
            String currentBlockString = "";
            if (current_block[0, 0] > 9)
            {
                currentBlockString += current_block[0, 0];
            }
            else
            {
                currentBlockString += "0" + current_block[0, 0];
            }
            if (current_block[1, 0] > 9)
            {
                currentBlockString += current_block[1, 0];
            }
            else
            {
                currentBlockString += "0" + current_block[1, 0];
            }
            if (current_block[2, 0] > 9)
            {
                currentBlockString += current_block[2, 0];
            }
            else
            {
                currentBlockString += "0" + current_block[2, 0];
            }
            if (current_block[3, 0] > 9)
            {
                currentBlockString += current_block[3, 0];
            }
            else
            {
                currentBlockString += "0" + current_block[3, 0];
            }
            if (current_block[0, 1] > 9)
            {
                currentBlockString += current_block[0, 1];
            }
            else
            {
                currentBlockString += "0" + current_block[0, 1];
            }
            if (current_block[1, 1] > 9)
            {
                currentBlockString += current_block[1, 1];
            }
            else
            {
                currentBlockString += "0" + current_block[1, 1];
            }
            if (current_block[2, 1] > 9)
            {
                currentBlockString += current_block[2, 1];
            }
            else
            {
                currentBlockString += "0" + current_block[2, 1];
            }
            if (current_block[3, 1] > 9)
            {
                currentBlockString += current_block[3, 1];
            }
            else
            {
                currentBlockString += "0" + current_block[3, 1];
            }
            return currentBlockString;
        }

        /// <summary>
        /// Kui mäng suletakse nurgast, siis kutsutakse see meetod välja, et sulgeda enne seda veel lahtised socketid.
        /// </summary>
        public void CloseSockets()
        {
            
            if(socket != null){
                socket.Dispose();
                if(socket.Connected)
                    socket.Close();
            }
            if(client != null){
                client.Dispose();
                if(client.Connected)
                    client.Close();
            }
        }

        /// <summary>
        /// Kui vastane on valmis mänguks, siis siin salvestatakse tema nimi
        /// </summary>
        /// <param name="nimi">vastase nimi kodeeritult</param>
        private void OpponentReady(string nimi)
        {
            opponentName = nimi.Substring(5);
            if (opponentName.Length > 11)
            {
                opponentName = opponentName.Substring(0, 11);
            }
            opponentReady = true;
        }

        /// <summary>
        /// Genereerib suvalise numbri antud vahemikus
        /// </summary>
        /// <param name="min">Min väärtus</param>
        /// <param name="max">Max väärtus</param>
        /// <returns>Tagastab suvalise numbri antud vahemikus</returns>
        public int RandomNumber(int min, int max)
        {
            return myRandom.Next(min, max);
        }

        /// <summary>
        /// Lisab platsi lõppu uue rea (vahetab vana välja)
        /// </summary>
        public void AddPunishmentline()
        {
            int[] nr = new int[RandomNumber(1, 4)];

            for (int i = 0; i < 10; i++)
            {
                mootor.board[19, i] = 1;
            }

            nr[0] = RandomNumber(0, 9);
            mootor.board[19, nr[0]] = 0;
            for (int i = 1; i < nr.GetLength(0); i++)
            {
                while (nr[i] == nr[i-1])
                {
                    nr[i] = RandomNumber(0,9);
                }
                mootor.board[19, nr[i]] = 0;
            }
        }
    }
}
